# ✅CMake指令只做配置，生成构建计划文件Makefiles：根据 CMakeLists.txt 中的 target_link_libraries、add_executable 等生成依赖规则
# 这个阶段不会真正去编译或链接库文件，只是生成“规则”，即告诉 make：main depends on libqrencode.dylib
# 所以即使文件不存在，CMake也不会报错，当执行make时，Make会根据生成的规则去实际编译和链接，这时如果找不到 libqrencode.dylib，就会报错：
# No rule to make target `/usr/local/opt/qrencode/lib/libqrencode.dylib', needed by `bin/main'

# ✅CMake 的指令和变量操作：
# 1️⃣ set(MAIN_SOURCES ...)：给MAIN_SOURCES赋值替换旧值覆盖原有内容，可以是单值，也可以是列表，列表本质上是以 ; 分隔的字符串。
# CMake的所有变量本质上都是字符串；
# 2️⃣ list(APPEND MAIN_SOURCES ...)：给MAIN_SOURCES列表追加附加新的元素，不会覆盖原有内容
# 3️⃣ add_executable(main ${MAIN_SOURCES})：创建一个可执行文件
# 4️⃣ if()/else()/endif()：条件语句
# 5️⃣ option(USE_FREERTOS "Use FreeRTOS for main" OFF)：option()是CMake内置指令，用来定义一个布尔型选项变量，
# 第一个参数是布尔型选项变量名，第二个参数是描述，第三个参数是默认值
# 6️⃣ file(GLOB_RECURSE ALL_SRC_C_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/xxx/*.c")
# file(GLOB_RECURSE …) ✅会递归查找最后一个参数目录及其所有子目录中的所有匹配文件的路径，加入 ALL_SRC_C_SOURCES 列表变量
# 7️⃣ file(GLOB C_FILE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/xxx/*.c")
# file(GLOB …) ✅只会递归查找最后一个参数一级目录中的所有匹配文件的路径，不会递归子目录，加入 C_FILE_SOURCES 列表变量

cmake_minimum_required(VERSION 3.12.4)

# VS Code按下 Ctrl+Shift+P快捷键，选择CMake: Edit User-Local CMake Kits进入编辑json文件cmake-tools-kits.json，确保"compilers"的C和CXX路径正确
# 否则CMake找不到指定的gcc.exe 和 g++.exe编译器，CMakeList.txt的project(lvgl C CXX)会报错：
# ❌CMake Error at CMakeLists.txt:10 (project):The CMAKE_C_COMPILER: F:/c++/EmbeddedDevelopment/msys64/mingw64/bin/gcc.exe is not a full path to an existing compiler tool.
# json文件cmake-tools-kits.json示例：
#   {
#     "name": "GCC 8.1.0 x86_64-w64-mingw32",
#     "compilers": {
#       "C": "F:\\c\\EmbeddedDevelopment\\msys64\\mingw64\\bin\\gcc.exe",
#       "CXX": "F:\\c\\EmbeddedDevelopment\\msys64\\mingw64\\bin\\g++.exe"
#     },
#     "isTrusted": true,
#     "environmentVariables": {
#       "CMT_MINGW_PATH": "F:\\c\\EmbeddedDevelopment\\msys64\\mingw64\\bin",
#       "MSYSTEM": "MINGW64",
#       "MSYSTEM_PREFIX": "/mingw64",
#       "PATH": "F:\\c\\EmbeddedDevelopment\\msys64\\mingw64\\bin;F:\\c++\\EmbeddedDevelopment\\usr\\bin;${env:PATH}"
#     },
#     "preferredGenerator": {
#       "name": "MinGW Makefiles"
#     }
#   }
project(lvgl C CXX)

# Set the correct FreeRTOS port for your system (e.g., Posix for WSL)
set(FREERTOS_PORT GCC_POSIX CACHE STRING "Port for FreeRTOS on Posix environment")

option(USE_FREERTOS "Enable FreeRTOS" OFF) # Turn this on to enable FreeRTOS

if(USE_FREERTOS)
    message(STATUS "FreeRTOS is enabled")

    # FreeRTOS integration when USE_FREERTOS is enabled
    add_library(freertos_config INTERFACE)
    target_include_directories(freertos_config SYSTEM INTERFACE)
    target_compile_definitions(freertos_config INTERFACE projCOVERAGE_TEST=0)

    # Add FreeRTOS as a subdirectory
    add_subdirectory(FreeRTOS)

    # FreeRTOS-specific include directories
    include_directories(${PROJECT_SOURCE_DIR}/FreeRTOS/include)
    include_directories(${PROJECT_SOURCE_DIR}/FreeRTOS/portable/ThirdParty/GCC/Posix)
    include_directories(${PROJECT_SOURCE_DIR}/config)

    # Add FreeRTOS sources
    file(GLOB FREERTOS_SOURCES
        "${PROJECT_SOURCE_DIR}/FreeRTOS/*.c"
        "${PROJECT_SOURCE_DIR}/FreeRTOS/portable/MemMang/heap_4.c"
        "${PROJECT_SOURCE_DIR}/FreeRTOS/portable/ThirdParty/GCC/Posix/*.c"
    )
else()
    message(STATUS "FreeRTOS is disabled")
    set(FREERTOS_SOURCES "")  # No FreeRTOS sources if FreeRTOS is disabled
endif()

# Define options for LVGL with default values (OFF)
option(LV_USE_DRAW_SDL "Use SDL draw unit" OFF)
option(LV_USE_LIBPNG "Use libpng to decode PNG" OFF)
option(LV_USE_LIBJPEG_TURBO "Use libjpeg turbo to decode JPEG" OFF)
option(LV_USE_FFMPEG "Use libffmpeg to display video using lv_ffmpeg" OFF)
option(LV_USE_FREETYPE "Use freetype library" OFF)

# Set C and C++ standards
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Set the output path for the executable
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
set(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})

# Add compile definitions based on the selected options
add_compile_definitions($<$<BOOL:${LV_USE_DRAW_SDL}>:LV_USE_DRAW_SDL=1>)
add_compile_definitions($<$<BOOL:${LV_USE_LIBPNG}>:LV_USE_LIBPNG=1>)
add_compile_definitions($<$<BOOL:${LV_USE_LIBJPEG_TURBO}>:LV_USE_LIBJPEG_TURBO=1>)
add_compile_definitions($<$<BOOL:${LV_USE_FFMPEG}>:LV_USE_FFMPEG=1>)

# Add LVGL subdirectory
add_subdirectory(lvgl)
target_include_directories(lvgl PUBLIC ${PROJECT_SOURCE_DIR})

set(MAIN_SOURCES library/hal/hal.c)

if(WIN32)
    message(STATUS "Building on Windows: disable lvgl demos for MinGW compatibility")
    set(MAIN_LIBS lvgl)  # Windows环境下不要 lvgl::demos
else()

# lvgl::examples 已经链接了 lvgl，lvgl::demos 又链接了 lvgl 和 lvgl::examples，lvgl::thorvg 可能也链接了 lvgl
# 重复链接库会⚠️警告如下：
# ld: warning: ignoring duplicate libraries: 'lvgl/lib/liblvgl.a', 'lvgl/lib/liblvgl_demos.a', 'lvgl/lib/liblvgl_examples.a', 'lvgl/lib/liblvgl_thorvg.a'

# main.c已注释掉lv_demo_widgets();，因此暂时没用到lvgl demos，此处只链接lvgl
    # set(MAIN_LIBS lvgl::examples lvgl::demos lvgl::thorvg)
    set(MAIN_LIBS lvgl)
endif()



if(NOT MSVC)
    list(APPEND MAIN_LIBS m pthread)
endif()

# Custom target to run the executable
# CMake命令add_custom_target用来创建一个自定义目标（custom target），名字为run
# COMMAND ${EXECUTABLE_OUTPUT_PATH}/main，即当执行 run 目标时，会运行这个命令相当于在终端里直接运行 ./main
add_custom_target(run COMMAND ${EXECUTABLE_OUTPUT_PATH}/main DEPENDS main)

# Conditionally include and link SDL2_image if LV_USE_DRAW_SDL is enabled
if(LV_USE_DRAW_SDL)
    set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
    find_package(SDL2_image REQUIRED)
    target_include_directories(lvgl PUBLIC ${SDL2_IMAGE_INCLUDE_DIRS})
    list(APPEND MAIN_LIBS ${SDL2_IMAGE_LIBRARIES})
endif()

# Conditionally include and link libpng if LV_USE_LIBPNG is enabled
if(LV_USE_LIBPNG)
    find_package(PNG REQUIRED)
    target_include_directories(lvgl PUBLIC ${PNG_INCLUDE_DIRS})
    list(APPEND MAIN_LIBS ${PNG_LIBRARIES})
endif()

# Conditionally include and link libjpeg-turbo if LV_USE_LIBJPEG_TURBO is enabled
if(LV_USE_LIBJPEG_TURBO)
    find_package(JPEG REQUIRED)
    target_include_directories(lvgl PUBLIC ${JPEG_INCLUDE_DIRS})
    list(APPEND MAIN_LIBS ${JPEG_LIBRARIES})
endif()

# Conditionally include and link FFmpeg libraries if LV_USE_FFMPEG is enabled
if(LV_USE_FFMPEG)
    list(APPEND MAIN_LIBS avformat avcodec avutil swscale z)
endif()

# Conditionally include and link FreeType if LV_USE_FREETYPE is enabled
if(LV_USE_FREETYPE)
    find_package(Freetype REQUIRED)
    target_include_directories(lvgl PUBLIC ${FREETYPE_INCLUDE_DIRS})
    list(APPEND MAIN_LIBS ${FREETYPE_LIBRARIES})
endif()


# =======================================✅ 开始第三方库链接第三方库依赖引用 ✅======================================= #

# Create the main executable, depending on the FreeRTOS option
if(USE_FREERTOS)
    list(APPEND MAIN_SOURCES src/freertos_main.c library/freertos/freertos_posix_port.c ${FREERTOS_SOURCES})
    list(APPEND MAIN_LIBS freertos_config freertos_kernel)
else()
    # include "src/main.h"头文件 (.h) 只是声明，告诉编译器函数、结构体、宏的存在，
    # 头文件不会生成机器码，也不会自动编译对应的.c文件，因此还需要把"src/main.c"加到编译列表里，❌否则函数实现找不到，链接会报错
    # 收集 src/ 目录及子目录的所有 .c 文件，✅file(GLOB_RECURSE …) 会递归查找子目录中的所有匹配文件。
    # ${CMAKE_CURRENT_SOURCE_DIR} 表示当前 CMakeLists.txt 所在的目录（更稳妥、跨平台）。
    # 匹配到的所有 .c 文件路径会加入 ALL_SRC_C_SOURCES 列表变量，列表本质上是以 ; 分隔的字符串。
    file(GLOB_RECURSE ALL_SRC_C_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c")
    # ALL_SRC_C_SOURCES列表变量添加到 MAIN_SOURCES 列表列表变量
    list(APPEND MAIN_SOURCES ${ALL_SRC_C_SOURCES})

    # 自动收集 assets/fonts/ 目录及子目录下的所有 .c 文件，✅file(GLOB_RECURSE ...) 会递归查找子目录中的所有匹配文件。
    # 匹配到的所有 .c 文件路径会加入 FONT_SOURCES 列表变量，列表本质上是以 ; 分隔的字符串。
    file(GLOB_RECURSE FONT_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/assets/fonts/*.c")
    # FONT_SOURCES列表变量添加到 MAIN_SOURCES 列表列表变量
    list(APPEND MAIN_SOURCES ${FONT_SOURCES})

    file(GLOB_RECURSE UTILS_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/utils/*.c")
    list(APPEND MAIN_SOURCES ${UTILS_SOURCES})

    file(GLOB IMAGE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/assets/images/*.c")
    list(APPEND MAIN_SOURCES ${IMAGE_SOURCES})

    file(GLOB MODEL_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/models/*.c")
    list(APPEND MAIN_SOURCES ${MODEL_SOURCES})

    list(APPEND
        MAIN_SOURCES
        # ✅下面这src文件夹底下的所有c文件已在上面的ALL_SRC_C_SOURCES变量列表中完成添加到MAIN_SOURCES列表
        # src/main.c
        # src/home/home.c
        # src/av_foundation/audio_play_ui.c
        # src/data_handle/data_handle.c

        # ✅下面这两个c文件已在上面的FONT_SOURCES变量列表中完成添加到MAIN_SOURCES列表
        # assets/fonts/single_color_emoji.c
        # assets/fonts/chinese_font.c
        # utils/network/http_client.c
        # utils/network/json_analysis.c
        # utils/components/navigation_bar.c
        # utils/components/sdl_disp_window.c
    )

endif()

#Q: ✅先通过CMake指令即add_executable设置需要编译的所有用到的.c文件得到.o可执行目标文件，
# 后面再通过CMake指令即target_link_libraries链接MAIN_LIBS
# 该link链接器把多个.o可执行目标文件及第三方库和系统库等MAIN_LIBS链接成可执行文件并命名为main
add_executable(main ${MAIN_SOURCES})
target_compile_definitions(main PRIVATE LV_CONF_INCLUDE_SIMPLE)

# 公共头文件，用于全局常量的宏定义
target_include_directories(lvgl PUBLIC ${PROJECT_SOURCE_DIR}/config)
# 公共头文件，用于公共数据结构的定义，例如通用的struct、typedef
target_include_directories(lvgl PUBLIC ${PROJECT_SOURCE_DIR}/include)

# Main include files of the project
# include_directories(${PROJECT_SOURCE_DIR}/main/inc)
include_directories(${PROJECT_SOURCE_DIR}/main/include) # include简称inc

# 链接第三方动态/静态库依赖
# 自动找到 brew 安装的第三方动态/静态库依赖的路径（包括头文件和库文件）
# Find and include SDL2 / CURL library
find_package(SDL2 REQUIRED) # MacOS和Windows系统都得设置 CMake 自动查找 sdl2
find_package(SDL2_mixer REQUIRED)
find_package(CURL REQUIRED) # MacOS和Windows系统都得设置 CMake 自动查找 libcurl
list(APPEND MAIN_LIBS CURL::libcurl SDL2::SDL2 SDL2_mixer::SDL2_mixer) # MacOS和Windows系统都得链接 libcurl & SDL2 动态/静态库
# Q: 通过target_link_libraries将main应用链接libcurl库，通过PRIVATE设置libcurl库的头文件路径不传播给main的下游即仅当前target使用

# ❌旧式CMake链接SDL2库
# 1️⃣find_package(SDL2 REQUIRED)
# 2️⃣list(APPEND MAIN_LIBS ${SDL2_LIBRARIES})
# 3️⃣target_include_directories(lvgl PUBLIC ${SDL2_INCLUDE_DIRS})
# ✅改用现代CMake链接SDL2库，只需一行代码，现代写法更可移植、更简洁，也避免了「变量名不同平台不一致」的问题。
# SDL2::SDL2是一个CMake target对象。它内部包含：头文件搜索路径；链接库路径；编译宏；平台特定设置（例如 -pthread）。
# 所以只需写一行代码CMake就能自动处理所有编译器选项和依赖。
# 1️⃣find_package(SDL2 REQUIRED)
# 2️⃣target_link_libraries(app PUBLIC SDL2::SDL2)

# ✅第三方库手动include_directories() + target_link_libraries()的处理；

# ✅libcurl和SDL2等库在许多系统／brew 安装后会自带 CMake 配置文件（例如 SDL2Config.cmake），
# SDL2使用 find_package(SDL2 REQUIRED) 后可直接使用 SDL2::SDL2 这个 target
# libcurl库使用find_package(CURL REQUIRED) 后使用 CURL::libcurl 这个 target
# 🎃 cJSON 虽然有 CMakeLists 支持，但其配置可能比较旧或不完善，可能出现报错如下：
# ❌“ Target "main" links to:  cJSON::cJSON  but the target was not found.” 这种情况
# 1️⃣先尝试find_package(cJSON REQUIRED) + cJSON::cJSON 这个 target；
# 2️⃣如果找不到，就手动做 include_directories() + target_link_libraries() 的方式。


set(system_name "Windows") # 用 set() 命令定义字符串变量system_name

# CMake 语法中，逻辑非运算符是 NOT，不是 !。
# if(!WIN32)
if(NOT WIN32)
    set(system_name "MacOS")
    # ✅MacOS系统通过Homebrew安装库：brew install sdl2 cjson curl

    # macOS 原有逻辑

    # cJSON库链接解决方案1️⃣如下：
    # ✅由于Homebrew安装第三方库存储路径在不同系统中可能不同，因此路径不能写死，此处先自动检测路径，OUTPUT_VARIABLE定义结果变量：
    execute_process(COMMAND brew --prefix cjson OUTPUT_VARIABLE CJSON_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE)
    execute_process(COMMAND brew --prefix openssl@3 OUTPUT_VARIABLE OPENSSL_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE)
    execute_process(COMMAND brew --prefix qrencode OUTPUT_VARIABLE QRENCODE_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE)

    # CMake指令输出打印日志到终端Terminal
    message("MacOS CJSON_PREFIX=${CJSON_PREFIX}")
    message("MacOS QRENCODE_PREFIX=${QRENCODE_PREFIX}")
    message("MacOS OPENSSL_PREFIX=${OPENSSL_PREFIX}")
    #CJSON_PREFIX=/usr/local/opt/cjson
    #CJSON_PREFIX=/usr/local/opt/qrencode
    #OPENSSL_PREFIX=/usr/local/opt/openssl@3

    include_directories(
        # /usr/local/opt/cjson
        # /usr/local/opt/openssl@3
        ${CJSON_PREFIX}
        ${OPENSSL_PREFIX}
        ${QRENCODE_PREFIX}/include
    )

    # 此处将库的dylib文件路径添加到MAIN_LIBS列表，后面需调用target_link_libraries(main PUBLIC ${MAIN_LIBS})
    # 必须用list(APPEND 附加到列表后面，不能用set(MAIN_LIBS xxx)会重新赋值替换旧值

    list(APPEND MAIN_LIBS
        ${QRENCODE_PREFIX}/lib/libqrencode.a
        ${CJSON_PREFIX}/lib/libcjson.dylib
        ${OPENSSL_PREFIX}/lib/libssl.dylib
        ${OPENSSL_PREFIX}/lib/libcrypto.dylib
        # /usr/local/opt/cjson/lib/libcjson.dylib
        # /usr/local/opt/openssl@3/lib/libssl.dylib
        # /usr/local/opt/openssl@3/lib/libcrypto.dylib
    )

    # cJSON库链接解决方案2️⃣如下：
    # find_package(cJSON REQUIRED)           # 尝试自动查找 cJSON
    # list(APPEND MAIN_LIBS cJSON::cJSON)     # 链接target

else()
    # Windows系统 MinGW64 的 MSYS2包管理器安装的第三方库
    set(CJSON_PREFIX "F:/c/EmbeddedDevelopment/msys64/mingw64")
    set(OPENSSL_PREFIX "F:/c/EmbeddedDevelopment/msys64/mingw64")
    set(LIBQRENCODE_PREFIX "F:/c/EmbeddedDevelopment/msys64/mingw64")
    # CMake指令输出打印日志到终端Terminal
    message("Windows CJSON_PREFIX=${CJSON_PREFIX}")
    message("Windows OPENSSL_PREFIX=${OPENSSL_PREFIX}")
    message("Windows OPENSSL_PREFIX=${LIBQRENCODE_PREFIX}")

    include_directories(
        ${CJSON_PREFIX}
        ${OPENSSL_PREFIX}
        ${LIBQRENCODE_PREFIX}
    )

    list(APPEND MAIN_LIBS
        ${CJSON_PREFIX}/lib/libcjson.a
        ${OPENSSL_PREFIX}/lib/libssl.a
        ${OPENSSL_PREFIX}/lib/libcrypto.a
        ${LIBQRENCODE_PREFIX}/lib/libqrencode.a
    )
endif()

message("Finish config third library path for ${system_name} system.")
# 无论cJSON采用哪种方案，最终都需将MAIN_LIBS列表中的所有dylib文件路径链接到target：
target_link_libraries(main PUBLIC ${MAIN_LIBS})


# =======================================✅ 完成第三方库链接第三方库依赖引用 ✅======================================= #


# On Windows, GUI applications do not show a console by default,
# which hides log output. This ensures a console is available for logging.
if (WIN32)
    if (MSVC)
        target_link_options(main PRIVATE "/SUBSYSTEM:CONSOLE")
    else()
        target_link_options(main PRIVATE "-mconsole")
    endif()
endif()

# Apply additional compile options if the build type is Debug
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    message(STATUS "Debug mode enabled")

    target_compile_options(lvgl PRIVATE
        -pedantic-errors
        -Wall
        -Wclobbered
        -Wdeprecated
        -Wdouble-promotion
        -Wempty-body
        -Wextra
        -Wformat-security
        -Wmaybe-uninitialized
        # -Wmissing-prototypes
        -Wpointer-arith
        -Wmultichar
        -Wno-pedantic # ignored for now, we convert functions to pointers for properties table.
        -Wreturn-type
        -Wshadow
        -Wshift-negative-value
        -Wsizeof-pointer-memaccess
        -Wtype-limits
        -Wundef
        -Wuninitialized
        -Wunreachable-code
        -Wfloat-conversion
        -Wstrict-aliasing
    )

if (ASAN)
    message(STATUS "AddressSanitizer enabled")

    # Add AddressSanitizer flags
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address")
else()
    message(STATUS "AddressSanitizer disabled")
endif()
endif()
